/**
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.gitee.huangjianbo.supos.openapi.httpclient5.httpclient;

import io.gitee.huangjianbo.supos.openapi.core.config.SuposApiClientConfig;
import io.gitee.huangjianbo.supos.openapi.core.config.SuposEndpoint;
import io.gitee.huangjianbo.supos.openapi.core.headers.CommonHeaders;
import io.gitee.huangjianbo.supos.openapi.core.http.HttpHeader;
import io.gitee.huangjianbo.supos.openapi.httpclient5.error.BuildHttpClientException;
import io.gitee.huangjianbo.supos.openapi.httpclient5.interceptors.AuthorizationHeaderInterceptor;
import io.gitee.huangjianbo.supos.openapi.httpclient5.interceptors.CustomHeadersInterceptor;

import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.TrustAllStrategy;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.config.Registry;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.http.message.BasicHeader;
import org.apache.hc.core5.io.CloseMode;
import org.apache.hc.core5.ssl.SSLContextBuilder;
import org.apache.hc.core5.util.TimeValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;

import static io.gitee.huangjianbo.supos.openapi.core.headers.CommonHeaders.getBuildVersion;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * HttpClient5的连接池管理类
 * 
 * @author tomcat
 * @version 1.0
 */
public final class HttpClient5Manager {

    private static final Logger LOGGER = LoggerFactory.getLogger(HttpClient5Manager.class);

    /**
     * 连接存储池
     */
    private final Map<String, CloseableHttpClient> CLIENTS = new ConcurrentHashMap<>();
    private final Map<String, PoolingHttpClientConnectionManager> CLIENT_MANAGERS = new ConcurrentHashMap<>();

    /**
     * Http Client的配置项
     */
    private static HttpClient5Config httpClientConfig = HttpClient5Config.createDefault();
    /**
     * 全局配置
     */
    private SuposApiClientConfig globalConfig;

    /**
     * 连接池监控器
     */
    private ScheduledExecutorService monitor;
    private static volatile boolean initialized = false;

    public HttpClient5Manager() {
        initialize();
    }

    public static void setHttpClientConfig(HttpClient5Config httpClientConfig) {
        HttpClient5Manager.httpClientConfig = httpClientConfig;
    }

    public void setGlobalConfig(SuposApiClientConfig globalConfig) {
        this.globalConfig = globalConfig;
    }

    /**
     * 创建请求客户端
     *
     * @param endpoint supOS请求地址
     * @return CloseableHttpClient
     */
    public CloseableHttpClient getClient(SuposEndpoint endpoint) {
        endpoint.validate();
        final String key = endpoint.getHost() + ":" + endpoint.getPort();
        return CLIENTS.computeIfAbsent(key, k -> createHttpClient(key, endpoint));
    }

    /**
     * 创建请求客户端
     *
     * @param key      存储KEY，以supOS的请求地址为对象
     * @param endpoint supOS请求地址
     * @return CloseableHttpClient
     */
    private CloseableHttpClient createHttpClient(String key, SuposEndpoint endpoint) {
        SSLContext sslContext = null;
        try {
            sslContext = SSLContextBuilder.create().loadTrustMaterial(TrustAllStrategy.INSTANCE).build();
        } catch (Exception e) {
            throw new BuildHttpClientException("Fail to build ssl context", e);
        }
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https", new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE))
                .build();

        HttpHost httpHost = new HttpHost(endpoint.getHost(), endpoint.getPort());

        RequestConfig requestConfig = RequestConfig.copy(RequestConfig.DEFAULT)
                .setContentCompressionEnabled(true)
                .setConnectionRequestTimeout(httpClientConfig.getConnectionTimeout(), TimeUnit.MILLISECONDS)
                .setResponseTimeout(httpClientConfig.getResponseTimeout(), TimeUnit.MILLISECONDS)
                .build();

        SocketConfig socketConfig = SocketConfig.copy(SocketConfig.DEFAULT)
                .setSoReuseAddress(true)
                .setTcpNoDelay(false)
                .setSoKeepAlive(false)
                .setSoTimeout(httpClientConfig.getSocketTimeout(), TimeUnit.MILLISECONDS)
                .setRcvBufSize(httpClientConfig.getRcvBufSize())
                .setSndBufSize(httpClientConfig.getSndBufSize())
                .build();

        PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager(registry);
        manager.setMaxTotal(200);
        manager.setMaxPerRoute(new HttpRoute(httpHost), 200);
        manager.setDefaultMaxPerRoute(200);
        manager.setDefaultSocketConfig(socketConfig);

        CloseableHttpClient httpClient = HttpClients.custom()
                .setConnectionManagerShared(true)
                .setConnectionManager(manager)
                .setDefaultRequestConfig(requestConfig)
                .setDefaultHeaders(getDefaultHeaders())
                .setUserAgent("java-openapi-sdk " + getBuildVersion())
                .addRequestInterceptorFirst(new CustomHeadersInterceptor(this.globalConfig))
                .addRequestInterceptorLast(new AuthorizationHeaderInterceptor(this.globalConfig))
                .build();

        CLIENT_MANAGERS.put(key, manager);

        LOGGER.debug("Create the http client for {}", key);

        return httpClient;
    }

    /**
     * 获取默认请求头
     *
     * @return 请求头列表
     */
    private Collection<? extends Header> getDefaultHeaders() {
        if (globalConfig == null) {
            return new ArrayList<>();
        }
        HttpHeader headers = CommonHeaders.getDefaultHeaders(globalConfig);
        return headers.toMap().entrySet().stream()
                .map(e -> new BasicHeader(e.getKey(), e.getValue()))
                .collect(Collectors.toList());
    }

    /**
     * 初始化连接池监控器
     */
    public void initialize() {
        if (!initialized) {
            initialized = true;

            LOGGER.debug("Go to initialize the connection pool monitor");

            monitor = Executors.newScheduledThreadPool(1);
            monitor.scheduleAtFixedRate(() -> {
                CLIENT_MANAGERS.forEach((k, v) -> {
                    v.closeExpired();
                    // Bug修复：将this.httpClientConfig.getMaxIdleTime()替换为静态访问方式HttpClient5Manager.httpClientConfig.getMaxIdleTime()
                    v.closeIdle(TimeValue.ofMilliseconds(HttpClient5Manager.httpClientConfig.getMaxIdleTime()));
                });
            }, 5, 30, TimeUnit.SECONDS);
        }
    }

    /**
     * 释放资源
     */
    public void close() {
        if (initialized && this.monitor != null) {
            LOGGER.debug("Go to release the connection pool monitor");
            this.monitor.shutdownNow();
        }
        if (!CLIENTS.isEmpty()) {
            LOGGER.debug("Go to release the http clients");
            CLIENTS.forEach((k, v) -> v.close(CloseMode.IMMEDIATE));
        }
        if (!CLIENT_MANAGERS.isEmpty()) {
            LOGGER.debug("Go to release the connection pool");
            CLIENT_MANAGERS.forEach((k, v) -> v.close(CloseMode.IMMEDIATE));
        }
    }
}
